bitkeeper revision 1.1108.2.17 (40ffde2eqJ67r1igRaAha9mzkh_gLA)
authortw275@labyrinth.cl.cam.ac.uk <tw275@labyrinth.cl.cam.ac.uk>
Thu, 22 Jul 2004 15:33:02 +0000 (15:33 +0000)
committertw275@labyrinth.cl.cam.ac.uk <tw275@labyrinth.cl.cam.ac.uk>
Thu, 22 Jul 2004 15:33:02 +0000 (15:33 +0000)
Removed async code from sv, and now used xend http interface for node info

tools/python/xen/sv/DomInfo.py
tools/python/xen/sv/DomList.py
tools/python/xen/sv/GenTabbed.py
tools/python/xen/sv/HTMLBase.py
tools/python/xen/sv/Main.py
tools/python/xen/sv/NodeInfo.py
tools/python/xen/sv/util.py
tools/python/xen/xend/XendClient.py

index a26736011c74a06867cac1b88d1a1ad8e243ebdb..42abdee799e22eb35aef19de3245cc8003cd3f17 100755 (executable)
@@ -1,20 +1,20 @@
-from HTMLBase import HTMLBase
-from XendClientDeferred import server
+from xen.xend.XendClient import aserver as server
 from xen.xend import PrettyPrint
 
+from xen.sv.HTMLBase import HTMLBase
 from xen.sv.util import *
 from xen.sv.GenTabbed import *
 
 class DomInfo( GenTabbed ):
 
-    def __init__( self, urlWriter, callback ):
+    def __init__( self, urlWriter ):
         
         self.dom = 0;
     
         def tabUrlWriter( tab ):
             return urlWriter( "mod=info&dom=%s%s" % ( self.dom, tab ) )
         
-        GenTabbed.__init__( self, tabUrlWriter, [ 'General', 'SXP', 'Devices' ], [ DomGenTab, DomSXPTab, NullTab ], callback  )
+        GenTabbed.__init__( self, tabUrlWriter, [ 'General', 'SXP', 'Devices' ], [ DomGenTab, DomSXPTab, NullTab ]  )
 
     def write_BODY( self, request ):
         dom = request.args.get('dom')
@@ -43,7 +43,8 @@ class DomGenTab( GeneralTab ):
     
         GeneralTab.__init__( self, "General Domain Info", {}, titles )
         
-    def write_BODY( self, request, callback ):
+    def write_BODY( self, request ):
+    
         dom = request.args.get('dom')
         
         if dom is None or len(dom) != 1:
@@ -52,14 +53,9 @@ class DomGenTab( GeneralTab ):
         else:
             self.dom = dom[0]
             
-        deferred = getDomInfoHash( self.dom )
-        deferred.addCallback( self.continue_BODY, request, callback )
-
-    def continue_BODY( self, dict, request, callback ):
-
-        self.dict = dict
+        self.dict = getDomInfoHash( self.dom )
         
-        GeneralTab.write_BODY( self, request, callback )
+        GeneralTab.write_BODY( self, request )
             
 class DomSXPTab( PreTab ):
 
@@ -67,21 +63,8 @@ class DomSXPTab( PreTab ):
         self.dom = 0
         PreTab.__init__( self, "" )
 
-    def fn( self, x, request ):
-        class tmp:
-            def __init__( self ):
-                self.str = ""
-            def write( self, str ):
-                self.str = self.str + str
-        temp = tmp()
-        PrettyPrint.prettyprint( x, out=temp )
-        self.source = temp.str
-        return request
-        
-    def fn2( self, request, callback ):
-        PreTab.write_BODY( self, request, callback )
-        
-    def write_BODY( self, request, callback ):
+
+    def write_BODY( self, request ):
         dom = request.args.get('dom')
         
         if dom is None or len(dom) != 1:
@@ -90,10 +73,10 @@ class DomSXPTab( PreTab ):
         else:
             self.dom = dom[0]
             
-        deferred = server.xend_domain( self.dom )
+        domInfo = server.xend_domain( self.dom )
+        
+        self.source = sxp2string( domInfo )
         
-        deferred.addCallback( self.fn, request )
-        deferred.addCallback( self.fn2, callback )
-        def errback( x ):
-            print ">err ", x
-        deferred.addErrback( errback )
+        PreTab.write_BODY( self, request )
+        
+
index 37229b6941c98f691e82798eabf2079229b224ea..ee55957ba6b7c543bdae4c0777897f029622f708 100755 (executable)
@@ -1,93 +1,54 @@
-from twisted.web import resource
-from twisted.web.server import NOT_DONE_YET
-
-from xen.xend.XendClient import server as XendServer
+from xen.xend.XendClient import server
 from xen.xend import sxp
 
 from xen.sv.HTMLBase import HTMLBase
 from xen.sv.util import *
 
-from twisted.internet import reactor
-
 class DomList( HTMLBase ):
     
     isLeaf = True
 
-    def __init__( self, urlWriter, callback ):
+    def __init__( self, urlWriter ):
         HTMLBase.__init__(self)
         self.urlWriter = urlWriter
-        self.head = None
-        self.long = None
-        self.rendered_domains = {}
-        self.domCount = 0
-        self.callback = callback
 
     def write_BODY( self, request, head=True, long=True ):
-        deferred = XendServer.xend_domains()
-        deferred.addCallback( self.get_domain_info, request )
-        deferred.addErrback( self.errback )
-        
-        self.head = head
-        self.long = long
-        
-    def errback( self, err ):
-        print 'errback>', err
-    
-    def get_domain_info( self, domains, request ):
-    
-        self.domCount = len( domains )
     
-        for domain in domains:
-            deferred = getDomInfoHash( domain )
-            deferred.addCallback( self.render_domain, request )
-            deferred.addErrback( self.errback )
-            
-    def render_domain( self, domInfoHash, request ):
+        domains = server.xend_domains()
     
-        domStr = "<td class='domainInfo' align='center'>%(dom)-4d</td>\n" % domInfoHash
-
-        url = self.urlWriter( "mod=info&dom=%(dom)-4d" % domInfoHash )
-                             
-        domStr += "<td class='domainInfo' align='center'><a href='%s'>%s</a></td>\n" % ( url, domInfoHash['name'] )
-        
-        if self.long: 
-            domStr += "<td class='domainInfo' align='center'>%(mem)7d</td>\n" % domInfoHash
-            domStr += "<td class='domainInfo' align='center'>%(cpu)3d</td>\n" % domInfoHash
-        
-        domStr += "<td class='domainInfo' align='center'>%(state)5s</td>\n" % domInfoHash
-        
-        if self.long:
-            domStr += "<td class='domainInfo' align='center'>%(cpu_time)7.1f</td>\n" % domInfoHash
-            
-        self.rendered_domains[ domInfoHash[ 'dom' ] ] = domStr
-        self.domCount -= 1
-        
-        if self.domCount == 0:
-            self.finish_write_BODY( request )
-        
-    def finish_write_BODY( self, request ):
-
         request.write( "\n<table style='border:0px solid white' cellspacing='0' cellpadding='0' border='0' width='100%'>\n" )
         
-        if self.head:
+        if head:
             request.write( "<tr class='domainInfoHead'>" )
-            self.write_DOMAIN_HEAD( request, self.long )
+            self.write_DOMAIN_HEAD( request, long )
             request.write( "</tr>" )
         
         odd = True
-        for domain in self.rendered_domains.values():
+        
+        for domain in domains:
             if odd:
                 request.write( "<tr class='domainInfoOdd'>\n" )
                 odd = False
             else:
                 request.write( "<tr class='domainInfoEven'>\n" )
                 odd = True
-            request.write( domain )
+            self.write_DOMAIN( request, getDomInfoHash( domain ), long )
             request.write( "</tr>\n" )
         
         request.write( "</table>\n" )
+            
+    def write_DOMAIN( self, request, domInfoHash, long=True ):   
+        request.write( "<td class='domainInfo' align='center'>%(dom)-4d</td>\n" % domInfoHash )
 
-        self.callback( request )
+        url = self.urlWriter( "mod=info&dom=%(dom)-4d" % domInfoHash )
+
+        request.write( "<td class='domainInfo' align='center'><a href='%s'>%s</a></td>\n" % ( url, domInfoHash['name'] ) )
+        if long: 
+            request.write( "<td class='domainInfo' align='center'>%(mem)7d</td>\n" % domInfoHash )
+            request.write( "<td class='domainInfo' align='center'>%(cpu)3d</td>\n" % domInfoHash )
+        request.write( "<td class='domainInfo' align='center'>%(state)5s</td>\n" % domInfoHash )
+        if long:
+            request.write( "<td class='domainInfo' align='center'>%(cpu_time)7.1f</td>\n" % domInfoHash )
 
     def write_DOMAIN_HEAD( self, request, long=True ):
         request.write( "<td class='domainInfoHead' align='center'>Domain</td>\n" )      
index 2c0dd1283f5a29fdca21f40ff6560e6aab5cefa4..bfbe5b04cf65be57d32ba3f895ee73920ae33640 100755 (executable)
@@ -5,13 +5,12 @@ from xen.sv.TabView import TabView
 
 class GenTabbed( HTMLBase ):
 
-    def __init__( self, urlWriter, tabStrings, tabObjects, callback ):
+    def __init__( self, urlWriter, tabStrings, tabObjects ):
         HTMLBase.__init__(self)
         self.tab = 0;
         self.tabStrings = tabStrings
         self.tabObjects = tabObjects
         self.urlWriter = urlWriter
-        self.callback = callback
 
     def write_BODY( self, request, urlWriter = None ):
         tab = request.args.get('tab')
@@ -34,29 +33,23 @@ class GenTabbed( HTMLBase ):
             request.write( "<p>Bad Tab</p>" )
             self.finish_BODY( request )
         else:
-            render_tab.write_BODY( request, self.finish_BODY )
+            render_tab.write_BODY( request )
 
-    def finish_BODY( self, request ):
-            
         request.write( "</td></tr></table>" )
         
-        self.callback( request )
-    
 class PreTab( HTMLBase ):
 
     def __init__( self, source ):
         HTMLBase.__init__( self )
         self.source = source
     
-    def write_BODY( self, request, callback ):
+    def write_BODY( self, request ):
         
         request.write( "<div style='display: block; overflow: auto; border: 0px solid black; height: 400px; width: 540px; padding: 5px; z-index:0; align: center'><pre>" )
         
         request.write( self.source )
         
         request.write( "</pre></div>" )
-        
-        callback( request )
 
 class GeneralTab( HTMLBase ):
                         
@@ -66,7 +59,7 @@ class GeneralTab( HTMLBase ):
         self.dict = dict
         self.titles = titles
                         
-    def write_BODY( self, request, callback ): 
+    def write_BODY( self, request ): 
         
         request.write( "<p><u>%s</u></p>" % self.title )
         
@@ -87,17 +80,14 @@ class GeneralTab( HTMLBase ):
             writeAttr( niceName, attr )
                             
         request.write( "</table>" )
-        
-        callback( request )
-    
+
 class NullTab( HTMLBase ):
     
     def __init__( self ):
         HTMLBase.__init__( self )
         self.title = "Null Tab"
         
-    def write_BODY( self, request, callback ):
+    def write_BODY( self, request ):
         request.write( "<p>%s</p>" % self.title )
-        callback( request )
-         
+
         
index e127dedac623b34013c0d910c3ac2e1eb9fd6d7c..4479d7e4558c9305edaddc1459832bf6d9cfb76b 100755 (executable)
@@ -1,27 +1,25 @@
-from twisted.web import server, resource
-from twisted.internet import reactor
+from twisted.web.resource import Resource
+
+class HTMLBase( Resource ):
 
-class HTMLBase( resource.Resource ):
-       
     isLeaf = True
-               
     def __init__( self ):
-        resource.Resource.__init__(self)
-               
+        Resource.__init__(self)
+
     def render_GET( self, request ):
         self.write_TOP( request )
-        return self.write_BODY( request, self.finish_render_GET )
-
-    def finish_render_GET( self, request ):
+        self.write_BODY( request )
         self.write_BOTTOM( request )
         request.finish()
+        return ''
                 
     def write_BODY( self, request ):
-               request.write( "BODY" )
+        request.write( "BODY" )
         
     def write_TOP( self, request ):
         request.write( '<html><head><title>Xen</title><link rel="stylesheet" type="text/css" href="inc/style.css" />' )
         request.write( '</head><body>' )
 
     def write_BOTTOM( self, request ):
-        request.write( "</body></html>" )
\ No newline at end of file
+        request.write( "</body></html>" )
index 1bbc3f0bfd1ec34f9fad536f57c09e9d6f9b1e31..6b9bbfae05dcd9b158555f6b9df21617bfc51261 100755 (executable)
@@ -1,11 +1,4 @@
-from twisted.web import resource
-from twisted.web.server import NOT_DONE_YET
-
-from xen.xend.XendClient import server as XendServer
-from xen.xend import sxp
-
-from HTMLBase import HTMLBase
-
+from xen.sv.HTMLBase import HTMLBase
 from xen.sv import DomList, NodeInfo, DomInfo
 
 class Main( HTMLBase ):
@@ -21,10 +14,8 @@ class Main( HTMLBase ):
     def mainUrlWriter( self, s ):
         return "Main.rpy?%s" % s
 
-    def write_BODY( self, request, callback ):
+    def write_BODY( self, request ):
     
-        self.callback = callback
-        
         request.write( "\n<table style='border:0px solid black; background: url(images/orb_01.jpg) no-repeat' cellspacing='0' cellpadding='0' border='0' width='780px' height='536px'>\n" )
         request.write( "<tr>\n" )
         request.write( " <td width='15px'>&nbsp;</td>" )
@@ -37,11 +28,8 @@ class Main( HTMLBase ):
         request.write( "    <p class='small'><a href='Main.rpy?mod=node'>Node details</a></p>" )
         request.write( "    <p class='small'><a href='Main.rpy?mod=list'>Domains summary</a></p>" )
     
-        DomList.DomList( self.mainUrlWriter, self.continue_BODY ).write_BODY( request, True, False )
+        DomList.DomList( self.mainUrlWriter ).write_BODY( request, True, False )
 
-        return NOT_DONE_YET
-        
-    def continue_BODY( self, request ):
         request.write( "   </td></tr>" )
         request.write( "  </table>" )
         request.write( " &nbsp;" )
@@ -56,18 +44,14 @@ class Main( HTMLBase ):
         
         if mod is None or len(mod) != 1:
             request.write( '<p>Please select a module</p>' )
-            self.finish_BODY( request )
         elif mod[0] == 'info':
-            DomInfo.DomInfo( self.mainUrlWriter, self.finish_BODY ).write_BODY( request )
+            DomInfo.DomInfo( self.mainUrlWriter ).write_BODY( request )
         elif mod[0] == 'list':
-            DomList.DomList( self.mainUrlWriter, self.finish_BODY ).write_BODY( request )
+            DomList.DomList( self.mainUrlWriter ).write_BODY( request )
         elif mod[0] == 'node':
-            NodeInfo.NodeInfo( self.mainUrlWriter, self.finish_BODY ).write_BODY( request )
+            NodeInfo.NodeInfo( self.mainUrlWriter ).write_BODY( request )
         else:
             request.write( '<p>Invalid module. Please select another</p>' )
-            self.finish_BODY( request )
-            
-    def finish_BODY( self, request ):
     
         request.write( "   </td></tr>" )
         request.write( "  </table>" )
@@ -77,5 +61,3 @@ class Main( HTMLBase ):
         
         request.write( "</table>\n" )
         
-        self.callback( request )
-        
index c0a569db587c75860e3036282d4624d70a9a1745..68d050b3bbd863a960d2a11d5380b798b9ae6fd7 100755 (executable)
@@ -1,24 +1,27 @@
-from xen.xend import XendDmesg
-from xen.xend import XendNode
+from xen.xend.XendClient import server
 
 from xen.sv.util import *
 from xen.sv.GenTabbed import *
-from xen.sv.HTMLBase  import HTMLBase
 
 class NodeInfo( GenTabbed ):
 
-    def __init__( self, urlWriter, callback ):
+    def __init__( self, urlWriter ):
     
         def newUrlWriter( url ):
             return urlWriter( "mod=node%s" % url )
     
-        GenTabbed.__init__( self, newUrlWriter, [ 'General', 'Dmesg' ], [ NodeGeneralTab, NodeDmesgTab ], callback )
+        GenTabbed.__init__( self, newUrlWriter, [ 'General', 'Dmesg' ], [ NodeGenTab, NodeDmesgTab ] )
 
+class NodeGenTab( PreTab ):
+    def __init__( self ):
+       text = sxp2string( server.xend_node() )
+       PreTab.__init__( self, text )            
+    
 class NodeGeneralTab( GeneralTab ):
                         
     def __init__( self ):
          
-        nodeInfo = XendNode.instance().info()
+        nodeInfo = server.xend_node()
         
         dictNodeInfo = {}
         
@@ -42,6 +45,6 @@ class NodeGeneralTab( GeneralTab ):
 class NodeDmesgTab( PreTab ):
 
     def __init__( self ):
-        self.xd = XendDmesg.instance()
-        PreTab.__init__( self, self.xd.info()[0] )
+        dmesg = server.xend_node_dmesg()
+        PreTab.__init__( self, dmesg[ 1 ] )
     
index 1d461c721b42db4cbcc32734146caa644db548bd..47d3519011e2b32bdeca56e8eaaae95f68a81a84 100755 (executable)
@@ -1,12 +1,9 @@
 from xen.xend.XendClient import server
 from xen.xend import sxp
+from xen.xend import PrettyPrint
 
 def getDomInfoHash( domain ):
-    deferred = server.xend_domain( int( domain ) )
-    deferred.addCallback( procDomInfo, domain )
-    return deferred
-    
-def procDomInfo( domInfo, domain ):
+    domInfo = server.xend_domain( int( domain ) )
     d = {}
     d['dom']    = int( domain )
     d['name']   = sxp.child_value( domInfo, 'name' )
@@ -19,7 +16,20 @@ def procDomInfo( domInfo, domain ):
     if( sxp.child_value( domInfo, 'start_time' ) ):
         d['start_time'] = float( sxp.child_value( domInfo, 'start_time' ) )
     return d
+
+def sxp2hash( sxp ):
+    pass
     
+def sxp2string( sxp ):
+    class tmp:
+        def __init__( self ):
+                self.str = ""
+        def write( self, str ):
+                self.str = self.str + str
+    temp = tmp()
+    PrettyPrint.prettyprint( sxp, out=temp )
+    return temp.str    
+
 def bigTimeFormatter( time ):
     weeks = time // 604800
     remainder = time % 604800
index 538f84c3a9218c775318305b150a97207df718c1..2d08f36056808075aa7a4f15060dd7e42f639728 100644 (file)
@@ -1,6 +1,8 @@
+#!/usr/bin/env python
 # Copyright (C) 2004 Mike Wray <mike.wray@hp.com>
 """Client API for the HTTP interface on xend.
 Callable as a script - see main().
+Supports synchronous or asynchronous connection to xend.
 
 This API is the 'control-plane' for xend.
 The 'data-plane' is done separately. For example, consoles
@@ -11,9 +13,12 @@ import sys
 import httplib
 import types
 from StringIO import StringIO
-import urlparse
+
 
 from twisted.protocols import http
+from twisted.internet.protocol import ClientCreator
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
 
 from encode import *
 import sxp
@@ -22,40 +27,22 @@ import PrettyPrint
 DEBUG = 0
 
 class XendError(RuntimeError):
-    pass
-
-class Foo(httplib.HTTPResponse):
-
-    def begin(self):
-        fin = self.fp
-        while(1):
-            buf = fin.readline()
-            print "***", buf
-            if buf == '':
-                print
-                sys.exit()
-
-
-def sxprio(sxpr):
-    """Convert an sxpr to a string.
+    """Error class for 'expected errors' when talking to xend.
     """
-    io = StringIO()
-    sxp.show(sxpr, out=io)
-    print >> io
-    io.seek(0)
-    return io
+    pass
 
 def fileof(val):
-    """Converter for passing configs.
+    """Converter for passing configs or other 'large' data.
     Handles lists, files directly.
     Assumes a string is a file name and passes its contents.
     """
     if isinstance(val, types.ListType):
-        return sxprio(val)
+        return sxp.to_string(val)
     if isinstance(val, types.StringType):
         return file(val)
     if hasattr(val, 'readlines'):
         return val
+    raise XendError('cannot convert value')
 
 # todo: need to sort of what urls/paths are using for objects.
 # e.g. for domains at the moment return '0'.
@@ -66,98 +53,253 @@ def fileof(val):
 # maps /xend/domain/0 to http://wray-m-3.hpl.hp.com:8000/xend/domain/0
 # And should accept urls for ids?
 
-def urljoin(location, root, prefix='', rest=''):
-    prefix = str(prefix)
-    rest = str(rest)
-    base = 'http://' + location + root + prefix
-    url = urlparse.urljoin(base, rest)
-    return url
+class URL:
+    """A URL.
+    """
 
-def nodeurl(location, root, id=''):
-    return urljoin(location, root, 'node/', id)
+    def __init__(self, proto='http', host='localhost', port=None, path='', query=None, frag=None):
+        self.proto = proto
+        self.host = host
+        if port: port = int(port)
+        self.port = port
+        self.path = path
+        self.query = query
+        self.frag = frag
+
+    def url(self):
+        """Get the full URL string including protocol, location and the full path.
+        """
+        return self.proto + '://' + self.location() + self.fullpath()
 
-def domainurl(location, root, id=''):
-    return urljoin(location, root, 'domain/', id)
+    def location(self):
+        """Get the location part of the URL, including host and port, if present.
+        """
+        if self.port:
+            return self.host + ':' + str(self.port)
+        else:
+            return self.host
 
-def consoleurl(location, root, id=''):
-    return urljoin(location, root, 'console/', id)
+    def fullpath(self):
+        """Get the full path part of the URL, including query and fragment if present.
+        """
+        u = [ self.path ]
+        if self.query:
+            u.append('?')
+            u.append(self.query)
+        if self.frag:
+            u.append('#')
+            u.append(self.frag)
+        return ''.join(u)
+
+    def relative(self, path='', query=None, frag=None):
+        """Create a URL relative to this one.
+        """
+        return URL(proto=self.proto,
+                   host=self.host,
+                   port=self.port,
+                   path=self.path + path,
+                   query=query,
+                   frag=frag)
+
+class XendRequest:
+    """A request to xend.
+    """
 
-def deviceurl(location, root, id=''):
-    return urljoin(location, root, 'device/', id)
+    def __init__(self, url, method, args):
+        """Create a request. Sets up the headers, argument data, and the
+        url.
 
-def vneturl(location, root, id=''):
-    return urljoin(location, root, 'vnet/', id)
+        @param url:    the url to request
+        @param method: request method, GET or POST
+        @param args:   dict containing request args, if any
+        """
+        if url.proto != 'http':
+            raise ValueError('Invalid protocol: ' + url.proto)
+        (hdr, data) = encode_data(args)
+        if args and method == 'GET':
+            url.query = data
+            data = None
+        if method == "POST" and url.path.endswith('/'):
+            url.path = url.path[:-1]
+
+        self.headers = hdr
+        self.data = data
+        self.url = url
+        self.method = method
+
+class XendClientProtocol:
+    """Abstract class for xend clients.
+    """
 
-def eventurl(location, root, id=''):
-    return urljoin(location, root, 'event/', id)
+    def xendRequest(self, url, method, args=None):
+        """Make a request to xend.
+        Implement in a subclass.
 
-def dmesgurl(location, root, id=''):
-    return urljoin(location, root, 'node/dmesg/', id)
+        @param url:    xend request url
+        @param method: http method: POST or GET
+        @param args:   request arguments (dict)
+        """
+        raise NotImplementedError()
 
-def xend_request(url, method, data=None):
-    """Make a request to xend.
+    def xendGet(self, url, args=None):
+        """Make a xend request using HTTP GET.
+        Requests using GET are usually 'safe' and may be repeated without
+        nasty side-effects.
 
-    url    xend request url
-    method http method: POST or GET
-    data   request argument data (dict)
-    """
-    urlinfo = urlparse.urlparse(url)
-    (uproto, ulocation, upath, uparam, uquery, ufrag) = urlinfo
-    if DEBUG: print url, urlinfo
-    if uproto != 'http':
-        raise StandardError('Invalid protocol: ' + uproto)
-    if DEBUG: print '>xend_request', ulocation, upath, method, data
-    (hdr, args) = encode_data(data)
-    if data and method == 'GET':
-        upath += '?' + args
-        args = None
-    if method == "POST" and upath.endswith('/'):
-        upath = upath[:-1]
-    if DEBUG: print "ulocation=", ulocation, "upath=", upath, "args=", args
-    #hdr['User-Agent'] = 'Mozilla'
-    #hdr['Accept'] = 'text/html,text/plain'
-    conn = httplib.HTTPConnection(ulocation)
-    #conn.response_class = Foo
-    if DEBUG: conn.set_debuglevel(1)
-    conn.request(method, upath, args, hdr)
-    resp = conn.getresponse()
-    if DEBUG: print resp.status, resp.reason
-    if DEBUG: print resp.msg.headers
-    if resp.status in [ http.NO_CONTENT ]:
-        return None
-    if resp.status not in [ http.OK, http.CREATED, http.ACCEPTED ]:
-        raise XendError(resp.reason)
-    pin = sxp.Parser()
-    data = resp.read()
-    if DEBUG: print "***data" , data
-    if DEBUG: print "***"
-    pin.input(data);
-    pin.input_eof()
-    conn.close()
-    val = pin.get_val()
-    #if isinstance(val, types.ListType) and sxp.name(val) == 'val':
-    #    val = val[1]
-    if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err':
-        raise XendError(val[1])
-    if DEBUG: print '**val='; sxp.show(val); print
-    return val
-
-def xend_get(url, args=None):
-    """Make a xend request using GET.
-    Requests using GET are 'safe' and may be repeated without
-    nasty side-effects.
+        @param url:    xend request url
+        @param data:   request arguments (dict)
+        """
+        return self.xendRequest(url, "GET", args)
+
+    def xendPost(self, url, args):
+        """Make a xend request using HTTP POST.
+        Requests using POST potentially cause side-effects, and should
+        not be repeated unless you really want to repeat the side
+        effect.
+
+        @param url:    xend request url
+        @param args:   request arguments (dict)
+        """
+        return self.xendRequest(url, "POST", args)
+
+    def handleStatus(self, version, status, message):
+        """Handle the status returned from the request.
+        """
+        status = int(status)
+        if status in [ http.NO_CONTENT ]:
+            return None
+        if status not in [ http.OK, http.CREATED, http.ACCEPTED ]:
+            return self.handleException(XendError(message))
+        return 'ok'
+
+    def handleResponse(self, data):
+        """Handle the data returned in response to the request.
+        """
+        if data is None: return None
+        try:
+            pin = sxp.Parser()
+            pin.input(data);
+            pin.input_eof()
+            val = pin.get_val()
+        except sxp.ParseError, err:
+            return self.handleException(err)
+        if isinstance(val, types.ListType) and sxp.name(val) == 'xend.err':
+            err = XendError(val[1])
+            return self.handleException(err)
+        return val
+
+    def handleException(self, err):
+        """Handle an exception during the request.
+        May be overridden in a subclass.
+        """
+        raise err
+
+class SynchXendClientProtocol(XendClientProtocol):
+    """A synchronous xend client. This will make a request, wait for
+    the reply and return the result.
     """
-    return xend_request(url, "GET", args)
 
-def xend_call(url, data):
-    """Make xend request using POST.
-    Requests using POST potentially cause side-effects and should
-    not be repeated unless it really is wanted to do the side
-    effect again.
+    def xendRequest(self, url, method, args=None):
+        """Make a request to xend.
+
+        @param url:    xend request url
+        @param method: http method: POST or GET
+        @param args:   request arguments (dict)
+        """
+        self.request = XendRequest(url, method, args)
+        conn = httplib.HTTPConnection(url.location())
+        if DEBUG: conn.set_debuglevel(1)
+        conn.request(method, url.fullpath(), self.request.data, self.request.headers)
+        resp = conn.getresponse()
+        val = self.handleStatus(resp.version, resp.status, resp.reason)
+        if val is None:
+            data = None
+        else:
+            data = resp.read()
+        conn.close()
+        val = self.handleResponse(data)
+        return val
+
+class AsynchXendClient(http.HTTPClient):
+    """A subclass of twisted's HTTPClient to deal with a connection to xend.
+    Makes the request when connected, and delegates handling responses etc.
+    to its protocol (usually an AsynchXendClientProtocol instance).
+    """
+    def __init__(self, protocol, request):
+        self.protocol = protocol
+        self.request = request
+
+    def connectionMade(self):
+        request = self.request
+        url = self.request.url
+        self.sendCommand(request.method, url.fullpath())
+        self.sendHeader('Host', url.location())
+        for (k, v) in request.headers.items():
+            self.sendHeader(k, v)
+        if request.data:
+            self.sendHeader('Content-Length', len(request.data))
+        self.endHeaders()
+        if request.data:
+            self.transport.write(request.data)
+
+    def handleStatus(self, version, status, message):
+        return self.protocol.handleStatus(version, status, message)
+
+    def handleResponse(self, data):
+        return self.protocol.handleResponse(data)
+
+class AsynchXendClientProtocol(XendClientProtocol):
+    """An asynchronous xend client. Uses twisted to connect to xend
+    and make the request. It does not block waiting for the result,
+    but sets up a deferred that is called when the result becomes available.
+
+    Uses AsynchXendClient to manage the connection.
     """
-    return xend_request(url, "POST", data)
+
+    def __init__(self):
+        self.err = None
+
+    def xendRequest(self, url, method, args=None):
+        """Make a request to xend. The returned deferred is called when
+        the result is available.
+
+        @param url:    xend request url
+        @param method: http method: POST or GET
+        @param args:   request arguments (dict)
+        @return: deferred
+        """
+        request = XendRequest(url, method, args)
+        self.deferred = Deferred()
+        clientCreator = ClientCreator(reactor, AsynchXendClient, self, request)
+        clientCreator.connectTCP(url.host, url.port)
+        return self.deferred
+
+    def callErrback(self, err):
+        if not self.deferred.called:
+            self.err = err
+            self.deferred.errback(err)
+        return err
+
+    def callCallback(self, val):
+        if not self.deferred.called:
+            self.deferred.callback(val)
+        return val
+
+    def handleException(self, err):
+        return self.callErrback(err)
+
+    def handleResponse(self, data):
+        if self.err: return self.err
+        val = XendClientProtocol.handleResponse(self, data)
+        if isinstance(val, Exception):
+            self.callErrback(val)
+        else:
+            self.callCallback(val)
+        return val
 
 class Xend:
+    """Client interface to Xend.
+    """
 
     """Default location of the xend server."""
     SRV_DEFAULT = "localhost:8000"
@@ -165,222 +307,288 @@ class Xend:
     """Default path to the xend root on the server."""
     ROOT_DEFAULT = "/xend/"
 
-    def __init__(self, srv=None, root=None):
+    def __init__(self, client=None, srv=None, root=None):
+        """Create a xend client interface.
+        If the client protocol is not specified, the default
+        is to use a synchronous protocol.
+
+        @param client:  client protocol to use
+        @param srv:     server host, and optional port (format host:port)
+        @param root:    xend root path on the server
+        """
+        if client is None:
+            client = SynchXendClientProtocol()
+        self.client = client
         self.bind(srv, root)
 
     def bind(self, srv=None, root=None):
         """Bind to a given server.
 
-        srv  server location (host:port)
-        root server xend root path
+        @param srv:  server location (host:port)
+        @param root: xend root path on the server
         """
         if srv is None: srv = self.SRV_DEFAULT
         if root is None: root = self.ROOT_DEFAULT
         if not root.endswith('/'): root += '/'
-        self.location = srv
-        self.root = root
+        (host, port) = srv.split(':', 1)
+        self.url = URL(host=host, port=port, path=root)
+
+    def xendGet(self, url, args=None):
+        return self.client.xendGet(url, args)
+
+    def xendPost(self, url, data):
+        return self.client.xendPost(url, data)
 
     def nodeurl(self, id=''):
-        return nodeurl(self.location, self.root, id)
+        return self.url.relative('node/' + str(id))
 
     def domainurl(self, id=''):
-        return domainurl(self.location, self.root, id)
+        return self.url.relative('domain/' + str(id))
 
     def consoleurl(self, id=''):
-        return consoleurl(self.location, self.root, id)
+        return self.url.relative('console/' + str(id))
 
     def deviceurl(self, id=''):
-        return deviceurl(self.location, self.root, id)
+        return self.url.relative('device/' + str(id))
 
     def vneturl(self, id=''):
-        return vneturl(self.location, self.root, id)
+        return self.url.relative('vnet/' + str(id))
 
     def eventurl(self, id=''):
-        return eventurl(self.location, self.root, id)
+        return self.url.relative('event/' + str(id))
 
     def dmesgurl(self, id=''):
-        return dmesgurl(self.location, self.root, id)
+        return self.url.relative('node/dmesg/' + str(id))
 
     def xend(self):
-        return xend_get(urljoin(self.location, self.root))
+        return self.xendGet(self.url)
 
     def xend_node(self):
-        return xend_get(self.nodeurl())
+        return self.xendGet(self.nodeurl())
+        
+    def xend_node_dmesg(self):
+        return self.xendGet(self.dmesgurl())
 
     def xend_node_cpu_rrobin_slice_set(self, slice):
-        return xend_call(self.nodeurl(),
-                         {'op'      : 'cpu_rrobin_slice_set',
-                          'slice'   : slice })
-    
+        return self.xendPost(self.nodeurl(),
+                             {'op'      : 'cpu_rrobin_slice_set',
+                              'slice'   : slice })
+
     def xend_node_cpu_bvt_slice_set(self, ctx_allow):
-        return xend_call(self.nodeurl(),
-                         {'op'      : 'cpu_bvt_slice_set',
-                          'ctx_allow' : ctx_allow })
-    
+        return self.xendPost(self.nodeurl(),
+                             {'op'      : 'cpu_bvt_slice_set',
+                              'ctx_allow' : ctx_allow })
+
     def xend_node_cpu_fbvt_slice_set(self, ctx_allow):
-        return xend_call(self.nodeurl(),
-                         {'op'      : 'cpu_fbvt_slice_set',
-                          'ctx_allow' : ctx_allow })
+        return self.xendPost(self.nodeurl(),
+                             {'op'      : 'cpu_fbvt_slice_set',
+                              'ctx_allow' : ctx_allow })
 
     def xend_domains(self):
-        return xend_get(self.domainurl())
+        return self.xendGet(self.domainurl())
 
     def xend_domain_create(self, conf):
-        return xend_call(self.domainurl(),
-                         {'op'      : 'create',
-                          'config'  : fileof(conf) })
+        return self.xendPost(self.domainurl(),
+                             {'op'      : 'create',
+                              'config'  : fileof(conf) })
 
     def xend_domain_restore(self, filename):
-        return xend_call(self.domainurl(),
-                         {'op'      : 'restore',
-                          'file'    : filename })
+        return self.xendPost(self.domainurl(),
+                             {'op'      : 'restore',
+                              'file'    : filename })
 
-    def xend_domain_configure(self, id, config):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'configure',
-                          'config'  : fileof(conf) })
+    def xend_domain_configure(self, id, conf):
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'configure',
+                              'config'  : fileof(conf) })
 
     def xend_domain(self, id):
-        return xend_get(self.domainurl(id))
+        return self.xendGet(self.domainurl(id))
 
     def xend_domain_unpause(self, id):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'unpause' })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'unpause' })
 
     def xend_domain_pause(self, id):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'pause' })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'pause' })
 
     def xend_domain_shutdown(self, id, reason):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'shutdown',
-                          'reason'  : reason })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'shutdown',
+                              'reason'  : reason })
 
     def xend_domain_destroy(self, id, reason):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'destroy',
-                          'reason'  : reason })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'destroy',
+                              'reason'  : reason })
 
     def xend_domain_save(self, id, filename):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'save',
-                          'file'    : filename })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'save',
+                              'file'    : filename })
 
     def xend_domain_migrate(self, id, dst):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'migrate',
-                          'destination': dst })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'migrate',
+                              'destination': dst })
 
     def xend_domain_pincpu(self, id, cpu):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'pincpu',
-                          'cpu'     : cpu })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'pincpu',
+                              'cpu'     : cpu })
 
     def xend_domain_cpu_bvt_set(self, id, mcuadv, warp, warpl, warpu):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'cpu_bvt_set',
-                          'mcuadv'  : mcuadv,
-                          'warp'    : warp,
-                          'warpl'   : warpl,
-                          'warpu'   : warpu })
-    
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'cpu_bvt_set',
+                              'mcuadv'  : mcuadv,
+                              'warp'    : warp,
+                              'warpl'   : warpl,
+                              'warpu'   : warpu })
+
     def xend_domain_cpu_fbvt_set(self, id, mcuadv, warp, warpl, warpu):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'cpu_fbvt_set',
-                          'mcuadv'  : mcuadv,
-                          'warp'    : warp,
-                          'warpl'   : warpl,
-                          'warpu'   : warpu })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'cpu_fbvt_set',
+                              'mcuadv'  : mcuadv,
+                              'warp'    : warp,
+                              'warpl'   : warpl,
+                              'warpu'   : warpu })
 
 
     def xend_domain_cpu_atropos_set(self, id, period, slice, latency, xtratime):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'cpu_atropos_set',
-                          'period'  : period,
-                          'slice'   : slice,
-                          'latency' : latency,
-                          'xtratime': xtratime })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'cpu_atropos_set',
+                              'period'  : period,
+                              'slice'   : slice,
+                              'latency' : latency,
+                              'xtratime': xtratime })
 
     def xend_domain_vifs(self, id):
-        return xend_get(self.domainurl(id),
-                        { 'op'      : 'vifs' })
-    
+        return self.xendGet(self.domainurl(id),
+                            { 'op'      : 'vifs' })
+
     def xend_domain_vif(self, id, vif):
-        return xend_get(self.domainurl(id),
-                        { 'op'      : 'vif',
-                          'vif'     : vif })
-    
+        return self.xendGet(self.domainurl(id),
+                            { 'op'      : 'vif',
+                              'vif'     : vif })
+
     def xend_domain_vbds(self, id):
-        return xend_get(self.domainurl(id),
-                        {'op'       : 'vbds'})
+        return self.xendGet(self.domainurl(id),
+                            {'op'       : 'vbds'})
 
     def xend_domain_vbd(self, id, vbd):
-        return xend_get(self.domainurl(id),
-                        {'op'       : 'vbd',
-                         'vbd'      : vbd })
+        return self.xendGet(self.domainurl(id),
+                            {'op'       : 'vbd',
+                             'vbd'      : vbd })
 
     def xend_domain_device_create(self, id, config):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'device_create',
-                          'config'  : fileof(config) })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'device_create',
+                              'config'  : fileof(config) })
 
     def xend_domain_device_destroy(self, id, type, idx):
-        return xend_call(self.domainurl(id),
-                         {'op'      : 'device_destroy',
-                          'type'    : type,
-                          'index'   : idx })
+        return self.xendPost(self.domainurl(id),
+                             {'op'      : 'device_destroy',
+                              'type'    : type,
+                              'index'   : idx })
 
     def xend_consoles(self):
-        return xend_get(self.consoleurl())
+        return self.xendGet(self.consoleurl())
 
     def xend_console(self, id):
-        return xend_get(self.consoleurl(id))
+        return self.xendGet(self.consoleurl(id))
 
     def xend_vnets(self):
-        return xend_get(self.vneturl())
+        return self.xendGet(self.vneturl())
 
     def xend_vnet_create(self, conf):
-        return xend_call(self.vneturl(),
-                         {'op': 'create', 'config': fileof(conf) })
+        return self.xendPost(self.vneturl(),
+                             {'op'      : 'create',
+                              'config'  : fileof(conf) })
 
     def xend_vnet(self, id):
-        return xend_get(self.vneturl(id))
+        return self.xendGet(self.vneturl(id))
 
     def xend_vnet_delete(self, id):
-        return xend_call(self.vneturl(id),
-                         {'op': 'delete' })
+        return self.xendPost(self.vneturl(id),
+                              {'op'     : 'delete' })
 
     def xend_event_inject(self, sxpr):
-        val = xend_call(self.eventurl(),
-                        {'op': 'inject', 'event': fileof(sxpr) })
+        val = self.xendPost(self.eventurl(),
+                             {'op'      : 'inject',
+                              'event'   : fileof(sxpr) })
 
     def xend_dmesg(self):
-        return xend_get(self.dmesgurl())
-    
+        return self.xendGet(self.dmesgurl())
+
+
+def xendmain(srv, asynch, fn, args):
+    if asynch:
+        client = AsynchXendClientProtocol()
+    else:
+        client = None
+    xend = Xend(srv=srv, client=client)
+    xend.rc = 0
+    try:
+        v = getattr(xend, fn)(*args)
+    except XendError, err:
+        print 'ERROR:', err
+        return 1
+    if asynch:
+        def cbok(val):
+            PrettyPrint.prettyprint(val)
+            reactor.stop()
+        def cberr(err):
+            print 'ERROR:', err
+            xend.rc = 1
+            reactor.stop()
+        v.addCallback(cbok)
+        v.addErrback(cberr)
+        reactor.run()
+        return xend.rc
+    else:
+        PrettyPrint.prettyprint(v)
+        return 0
 
 def main(argv):
     """Call an API function:
-    
+
     python XendClient.py fn args...
 
     The leading 'xend_' on the function can be omitted.
     Example:
 
-    > python XendClient.py domains
-    (domain 0 8)
-    > python XendClient.py domain 0
+python XendClient.py domains
+    (0 8)
+python XendClient.py domain 0
     (domain (id 0) (name Domain-0) (memory 128))
     """
-    server = Xend()
-    fn = argv[1]
+    global DEBUG
+    from getopt import getopt
+    short_options = 'x:ad'
+    long_options = ['xend=', 'asynch', 'debug']
+    (options, args) = getopt(argv[1:], short_options, long_options)
+    srv = None
+    asynch = 0
+    for k, v in options:
+        if k in ['-x', '--xend']:
+            srv = v
+        elif k in ['-a', '--asynch']:
+            asynch = 1
+        elif k in ['-d', '--debug']:
+            DEBUG = 1
+    if len(args):
+        fn = args[0]
+        args = args[1:]
+    else:
+        fn = 'xend'
+        args = []
     if not fn.startswith('xend'):
         fn = 'xend_' + fn
-    args = argv[2:]
-    val = getattr(server, fn)(*args)
-    PrettyPrint.prettyprint(val)
-    print
+    sys.exit(xendmain(srv, asynch, fn, args))
 
 if __name__ == "__main__":
     main(sys.argv)
 else:    
     server = Xend()
+    aserver = Xend( AsynchXendClientProtocol() )